#include "Log.h"

#include <sstream>
#include "Topic.h"
#include "recovery/fs/file/FileLogOperation.h"
#include "CheckSum.h"


using recovery::Status;
using recovery::fs::file::FileLogOperation;


// class LogSize ............

LogSize::LogSize(IMeta & meta)
: ILogSize(0)
, _meta(meta)
{
}
LogSize::~LogSize()
{
}

size_t LogSize::value() const  {
    return ILogSize::value(); 
}

void LogSize::value(size_t size)  { 
    ILogSize::value(size);
    _meta.synced(false);
}

ILogSize & LogSize::overwrite(size_t fileSize) {
    value(fileSize);
    return *this;
}
recovery::Status LogSize::sync(){
    return _meta.sync();
}

// class Meta ............
Meta::Meta(Topic & topic)
: _topic(topic)
, _logSize(new LogSize(*this))
{
}
Meta::~Meta() {
    delete _logSize;
}

recovery::Status Meta::open(){
	if(!isOpen()){
		if(_topic.open() == 0){
			//1.no-exist then create it  or open exist log it 
			auto s = FileLogOperation::exist(_topic.metaPath()) ? 
				FileLogOperation::openExist(_topic.metaPath(), _file).toStatus(): 
				FileLogOperation::create(_topic.metaPath(), _file).toStatus();
			_opened = true;
			return s;
		}
		return Status::Failed("open topic failed");
	}
	return Status::OK();
}


recovery::Status Meta::close(){
	if(isOpen()){
		if(_file->close()){
			if(_topic.close() == 0){ 
				_opened = false;
				return Status::OK(); 
			}
			return Status::Failed("Topic close failed");
		}
		return Status::Failed("meta file close failed");
	}
	return Status::OK();
}

//Only for persisit
struct LogInfo {
public:
	LogInfo() {}
	~LogInfo() {}

	static constexpr size_t size() { return sizeof(size_t); }
	const uint8_t * data() const { return reinterpret_cast<const uint8_t*>(&_bodySize); }


	void bodySize(size_t bodySize) { this->_bodySize = bodySize; }
	size_t bodySize() const { return this->_bodySize; }

	void reset(){ 
		_bodySize = 0; 
	}

	std::string toString() const {
		std::ostringstream oss;
		oss << "sizeof(LogHeader):" << size() << " LogBodySize:" << bodySize() << std::endl;
		return oss.str();
	}
	recovery::Status read(std::unique_ptr<recovery::fs::file::FileLogOperation> & file) const {
		size_t readBytes = 0;
		uint8_t * ptr = const_cast<uint8_t*>(data());
		return file->positionedRead(0, ptr, LogInfo::size(), readBytes).toStatus();
	}
	recovery::Status write(std::unique_ptr<recovery::fs::file::FileLogOperation> & file) const {
		size_t writedBytes = 0;
		return file->positionedWrite(0, data(), LogInfo::size(), writedBytes).toStatus();
	}
private:
	size_t _bodySize = 0;
	//TODO:  size_t _next_trans_id = 0;
};


Status Meta::load() {
	if(isOpen()){
		size_t fileSize = 0;
		auto s = _file->size(fileSize).toStatus();
		if(!s) return s;

        LogInfo info;
		if(fileSize >= this->size()){
			s = info.read(_file);
			if(!s) return s;
		}
		// filesize < this->size() reset the Meta to 0
        else {
            s = info.write(_file);
            if(!s) return s;
        }

        //load all values in this;
        logSize().value(info.bodySize());

        this->synced(true);
        return Status::OK();
	}
	return Status::Failed("open it first");
}
recovery::Status Meta::reset(){
	if(isOpen()){
		logSize().value(0);

		LogInfo info;
		auto s = info.write(_file);
		if(!s) return s;

		this->synced(true);
		return Status::OK();
	}
	return Status::Failed("open it first");
}
Status Meta::sync() {
	if(isOpen()){
		LogInfo info;
		info.bodySize(logSize().value());
		auto s = info.write(_file);
		if(!s) return s;

		this->synced(true);
		return Status::OK();
	}
	return Status::Failed("open it first");
}
size_t Meta::size() const {
	return LogInfo::size();
}


// class Log

Log::Log(Topic & topic)
: _topic(topic)
, _meta(IMeta::create(topic))
{
}
Log::~Log() {
    delete _meta;
}

recovery::Status Log::open() {
    if(0 == _topic.open()){
        auto s = FileLogOperation::exist(_topic.logPath()) ? 
            FileLogOperation::openExist(_topic.logPath(), _log).toStatus() : 
            FileLogOperation::create(_topic.logPath(), _log).toStatus();
        if(!s) return s;

        //Restart logic ???  TODO: extract to outside
        size_t logFileSize = 0;
        s = _log->size(logFileSize).toStatus();
        if(!s) return s;

        s =_meta->open();
        if(!s) return s;

        s = _meta->load();
        if(!s)  return s;

        if(logFileSize > _meta->logSize().value()){
            _log->truncate(_meta->logSize().value());
        }
        return _log->seekEnd();
    }
    return Status::Failed("open topic failed!");
}
recovery::Status Log::close() {
    if(_log->close()){
        if(_meta->close()){
            return _topic.close() == 0 ? Status::OK() : Status::Failed("Topic close failed");
        }
        else
            return Status::Failed("meta file close failed");
    }
    return Status::Failed("log close failed");
}



class TransHeader {
public:
	TransHeader(const std::string & data) 
    : _bodySize(data.size())
    {
        CheckSum sum;
        _checksum = sum.crc32(data);
    }
	~TransHeader(){}
	static constexpr size_t HeaderSize() {return sizeof(size_t) + sizeof(size_t) + sizeof(uint32_t);}

	size_t size() const { return TransHeader::HeaderSize(); }
	const uint8_t * data() const { return reinterpret_cast<const uint8_t*>(&_Id);}
	void setId(size_t Id) { this->_Id = Id;}
	void setBodySize(const size_t bodySize) { this->_bodySize = bodySize; }
    void setCheckSum(const uint32_t checksum) { this-> _checksum = checksum; }
    uint32_t checksum() const { return this->_checksum; }
private:
	size_t _Id = 0;
	size_t _bodySize = 0;
    uint32_t _checksum = 0;
};
recovery::Status Log::append(const std::string & data, size_t & appendedBytes)  {
    TransHeader transHeader(data);
    //write trans_header to log
    if(_log->write(transHeader.data(), transHeader.size(), appendedBytes)){
        size_t bytes = 0;
        auto s = _log->write(data, bytes);
        if(s){
            appendedBytes += bytes;
        }
        return s.toStatus();
    }
    return Status::Failed("write transaction header error!");
}
recovery::Status Log::sync()  {
    return _log->sync().toStatus();
}
recovery::Status Log::truncate(size_t size)  {
    return _log->truncate(size).toStatus();
}

recovery::Status Log::size(size_t & size) const  {
    return _log->size(size).toStatus();
}
